#include "SkrProfile/profile.h"
#include "SkrBase/misc/make_zeroed.hpp"
#include "SkrCore/time.h"
#include "SkrCore/platform/vfs.h"
#include "SkrRT/ecs/query.hpp"
#include "SkrRenderer/render_device.h"
#include "SkrLive2D/l2d_renderer.hpp"
#include "SkrLive2D/l2d_render_model.h"
#include "SkrLive2D/skr_live2d.h"

#include "live2d_model_pass.hpp"
#include "live2d_mask_pass.hpp"

#include "Framework/Math/CubismMatrix44.hpp"
#include "live2d_clipping.hpp"
#include "live2d_helpers.hpp"

static struct RegisterComponentskr_live2d_render_model_comp_tHelper
{
    RegisterComponentskr_live2d_render_model_comp_tHelper()
    {
        using namespace skr::literals;

        sugoi_type_description_t desc = make_zeroed<sugoi_type_description_t>();
        desc.name = u8"skr_live2d_render_model_comp_t";

        desc.size = sizeof(skr_live2d_render_model_comp_t);
        desc.entityFieldsCount = 1;
        static intptr_t entityFields[] = { 0 };
        desc.entityFields = entityFields;
        desc.guid = u8"63524b75-b86d-4b34-ba59-b600eb4b415b"_guid;
        desc.callback = {};
        desc.flags = 0;
        desc.elementSize = 0;
        desc.alignment = alignof(skr_live2d_render_model_comp_t);
        type = sugoiT_register_type(&desc);
    }
    sugoi_type_index_t type = SUGOI_NULL_TYPE;
} _RegisterComponentskr_live2d_render_model_comp_tHelper;

SKR_LIVE2D_API sugoi_type_index_t sugoi_id_of<skr_live2d_render_model_comp_t>::get()
{
    return _RegisterComponentskr_live2d_render_model_comp_tHelper.type;
}

void skr_live2d_render_view_reset(live2d_render_view_t* view);
void skr_live2d_render_view_set_screen(live2d_render_view_t* view, uint32_t width, uint32_t height);
void skr_live2d_render_view_transform_screen(live2d_render_view_t* view, float deviceX, float deviceY);
void skr_live2d_render_view_transform_view(live2d_render_view_t* view, float deviceX, float deviceY);

struct Live2DRendererImpl : public skr::Live2DRenderer
{
    uint32_t last_ms = 0;
    const float kMotionFramesPerSecond = 240.0f;
    const bool kUseHighPrecisionMask = false;

    skr::Map<skr_live2d_render_model_id, skr::span<const uint32_t>> sorted_drawable_list;
    skr::Map<skr_live2d_render_model_id, skr::InlineVector<uint32_t, 4>> sorted_mask_drawable_lists;
    skr::Map<skr_live2d_render_model_id, STimer> motion_timers;
    skr::Vector<skr_primitive_draw_t> model_drawcalls;
    skr::Vector<skr_primitive_draw_t> mask_drawcalls;

    skr_vfs_t* resource_vfs = nullptr;
    const char8_t* push_constants_name = u8"push_constants";
    live2d_render_view_t view_;

    skr::ecs::EntityQuery* effect_query = nullptr;
    double sample_count = 1.0;
    uint64_t frame_count = 0;
    uint64_t async_slot_index = 0;

    void initialize(skr::RenderDevice* render_device, skr::ecs::World* world, struct skr_vfs_t* resource_vfs) override
    {
        this->resource_vfs = resource_vfs;
        effect_query = skr::ecs::QueryBuilder(world)
                           .ReadAll<skr_live2d_render_model_comp_t>()
                           .commit()
                           .value();

        // prepare render resources
        prepare_pipeline_settings();
        prepare_pipeline(render_device);
        prepare_mask_pipeline(render_device);
        // prepare_geometry_resources(renderer);
        skr_live2d_render_view_reset(&view_);
    }

    void finalize(skr::RenderDevice* renderer) override
    {
        auto sweepFunction = [&](sugoi_chunk_view_t* r_cv) {
            auto meshes = sugoi::get_owned_ro<skr_live2d_render_model_comp_t>(r_cv);
            for (uint32_t i = 0; i < r_cv->count; i++)
            {
                while (!meshes[i].vram_future.is_ready()) {}
                if (meshes[i].vram_future.render_model)
                {
                    skr_live2d_render_model_free(meshes[i].vram_future.render_model);
                }
                while (!meshes[i].ram_future.is_ready()) {}
                if (meshes[i].ram_future.model_resource)
                {
                    skr_live2d_model_free(meshes[i].ram_future.model_resource);
                }
            }
        };
        sugoiQ_get_views(effect_query, SUGOI_LAMBDA(sweepFunction));
        free_pipeline(renderer);
        free_mask_pipeline(renderer);
    }

    void draw(skr::render_graph::RenderGraph* render_graph) override
    {
        Live2DRenderPass::create_frame_resources(render_graph);
        Live2DMaskPass::create_frame_resources(render_graph);

        Live2DRenderPass::execute(render_graph, model_drawcalls);
        Live2DMaskPass::execute(render_graph, mask_drawcalls);
    }

    void produce_drawcalls(sugoi_storage_t* storage, skr::render_graph::RenderGraph* render_graph) override
    {
        frame_count++;
        async_slot_index = frame_count % RG_MAX_FRAME_IN_FLIGHT;
        {
            SkrZoneScopedN("ProduceMaskDrawPackets");
            produce_mask_drawcall(storage, render_graph);
        }
        {
            SkrZoneScopedN("ProduceModelDrawPackets");
            produce_model_drawcall(storage, render_graph);
        }
    }

    void produce_model_drawcall(sugoi_storage_t* storage, skr::render_graph::RenderGraph* render_graph)
    {
        CubismMatrix44 projection;
        // TODO: Correct Projection
        projection.Scale(static_cast<float>(100.f) / static_cast<float>(100.f), 1.0f);
        // TODO: View Matrix
        model_drawcalls.resize_zeroed(0);
        auto counterF = [&](sugoi_chunk_view_t* r_cv) {
            auto models = sugoi::get_owned_ro<skr_live2d_render_model_comp_t>(r_cv);
            const auto proper_pipeline = get_pipeline();
            for (uint32_t i = 0; i < r_cv->count; i++)
            {
                if (models[i].vram_future.is_ready())
                {
                    auto&& render_model = models[i].vram_future.render_model;
                    const auto& cmds = render_model->primitive_commands;
                    push_constants.try_add_default(render_model).value().resize(0);

                    auto&& model_resource = models[i].ram_future.model_resource;
                    const auto list = skr_live2d_model_get_sorted_drawable_list(model_resource);
                    if (!list) continue;

                    auto drawable_list = sorted_drawable_list.add(render_model, { list, render_model->index_buffer_views.size() }).value();
                    push_constants.find(render_model).value().resize(drawable_list.size());
                    // record constant parameters
                    auto clipping_manager = render_model->clipping_manager;
                    if (auto clipping_list = clipping_manager->GetClippingContextListForDraw())
                    {
                        for (auto drawable : drawable_list)
                        {
                            const auto& cmd = cmds[drawable];
                            auto& push_const = push_constants.find(render_model).value()[drawable];
                            CubismClippingContext* clipping_context = (*clipping_list)[drawable];
                            push_const.use_mask = clipping_context && clipping_context->_isUsing;
                            if (push_const.use_mask)
                            {
                                const rtm::matrix4x4f clip_mat = rtm::matrix_set(
                                    rtm::vector_load(&clipping_context->_matrixForDraw.GetArray()[4 * 0]),
                                    rtm::vector_load(&clipping_context->_matrixForDraw.GetArray()[4 * 1]),
                                    rtm::vector_load(&clipping_context->_matrixForDraw.GetArray()[4 * 2]),
                                    rtm::vector_load(&clipping_context->_matrixForDraw.GetArray()[4 * 3]));
                                push_const.clip_matrix = rtm::matrix_transpose(clip_mat);
                                const csmInt32 channelNo = clipping_context->_layoutChannelNo;
                                CubismRenderer::CubismTextureColor* colorChannel = clipping_context->GetClippingManager()->GetChannelFlagAsColor(channelNo);
                                push_const.channel_flag = { colorChannel->R, colorChannel->G, colorChannel->B, colorChannel->A };
                            }
                            const rtm::matrix4x4f proj_mat = rtm::matrix_set(
                                rtm::vector_load(&projection.GetArray()[4 * 0]),
                                rtm::vector_load(&projection.GetArray()[4 * 1]),
                                rtm::vector_load(&projection.GetArray()[4 * 2]),
                                rtm::vector_load(&projection.GetArray()[4 * 3]));
                            push_const.projection_matrix = rtm::matrix_transpose(proj_mat);
                            skr_live2d_model_get_drawable_colors(render_model->model_resource_id, drawable, &push_const.multiply_color, &push_const.screen_color);
                            push_const.base_color = { 1.f, 1.f, 1.f, push_const.multiply_color.w };

                            auto visibility = skr_live2d_model_get_drawable_is_visible(render_model->model_resource_id, drawable);
                            auto& drawcall = model_drawcalls.emplace().ref();
                            if (!visibility)
                            {
                                drawcall.deprecated = true;
                                drawcall.pipeline = proper_pipeline;
                            }
                            else
                            {
                                drawcall.pipeline = proper_pipeline;
                                drawcall.push_const_name = push_constants_name;
                                drawcall.push_const = (const uint8_t*)(push_constants.find(render_model).value().data() + drawable);
                                drawcall.index_buffer = *cmd.ibv;
                                drawcall.vertex_buffers = cmd.vbvs.data();
                                drawcall.vertex_buffer_count = (uint32_t)cmd.vbvs.size();
                                {
                                    auto texture_view = skr_live2d_render_model_get_texture_view(render_model, drawable);
                                    drawcall.bind_table = render_model->bind_tables.find(texture_view).value();
                                }
                            }
                        }
                    }
                }
            }
        };
        sugoiQ_get_views(effect_query, SUGOI_LAMBDA(counterF));
    }

    void produce_mask_drawcall(sugoi_storage_t* storage, skr::render_graph::RenderGraph* render_graph)
    {
        {
            SkrZoneScopedN("FrameCleanUp");

            mask_drawcalls.resize_zeroed(0);
            sorted_mask_drawable_lists.clear();
        }
        auto updateMaskF = [&](sugoi_chunk_view_t* r_cv) {
            SkrZoneScopedN("UpdateMaskF");

            const auto proper_pipeline = get_mask_pipeline();
            auto models = sugoi::get_owned_ro<skr_live2d_render_model_comp_t>(r_cv);
            for (uint32_t i = 0; i < r_cv->count; i++)
            {
                if (models[i].vram_future.is_ready())
                {
                    auto&& render_model = models[i].vram_future.render_model;
                    auto&& model_resource = models[i].ram_future.model_resource;
                    if (!mask_push_constants.contains(render_model))
                        mask_push_constants.try_add_default(render_model);
                    mask_push_constants.find(render_model).value().resize(0);

                    // TODO: move this to (some manager?) other than update morph/phys in a render pass
                    updateModelMotion(render_graph, render_model);
                    updateTexture(render_model);
                    // record constant parameters
                    if (auto clipping_manager = render_model->clipping_manager)
                    {
                        auto mask_list = clipping_manager->GetClippingContextListForMask();
                        if (!mask_list) continue;
                        uint32_t valid_masks = 0;
                        for (uint32_t j = 0; j < mask_list->GetSize(); j++)
                        {
                            CubismClippingContext* clipping_context = (clipping_manager != NULL) ? (*mask_list)[j] : NULL;
                            if (clipping_context && !kUseHighPrecisionMask && clipping_context->_isUsing)
                            {
                                const csmInt32 clipDrawCount = clipping_context->_clippingIdCount;
                                for (csmInt32 ctx = 0; ctx < clipDrawCount; ctx++)
                                {
                                    const csmInt32 clipDrawIndex = clipping_context->_clippingIdList[ctx];
                                    // 頂点情報が更新されておらず、信頼性がない場合は描画をパスする
                                    if (!model_resource->model->GetModel()->GetDrawableDynamicFlagVertexPositionsDidChange(clipDrawIndex))
                                    {
                                        continue;
                                    }
                                    valid_masks++;
                                }
                            }
                        }
                        sorted_mask_drawable_lists.reserve(valid_masks);
                        mask_push_constants.reserve(valid_masks);
                        for (uint32_t j = 0; j < mask_list->GetSize(); j++)
                        {
                            CubismClippingContext* clipping_context = (clipping_manager != NULL) ? (*mask_list)[j] : NULL;
                            if (clipping_context && !kUseHighPrecisionMask && clipping_context->_isUsing)
                            {
                                const csmInt32 clipDrawCount = clipping_context->_clippingIdCount;
                                for (csmInt32 ctx = 0; ctx < clipDrawCount; ctx++)
                                {
                                    const csmInt32 clipDrawIndex = clipping_context->_clippingIdList[ctx];
                                    // 頂点情報が更新されておらず、信頼性がない場合は描画をパスする
                                    if (!model_resource->model->GetModel()->GetDrawableDynamicFlagVertexPositionsDidChange(clipDrawIndex))
                                    {
                                        continue;
                                    }

                                    sorted_mask_drawable_lists.try_add_default(render_model).value().emplace(clipDrawIndex);
                                    auto&& push_const = mask_push_constants.find(render_model).value().emplace_back();
                                    const rtm::matrix4x4f proj_mat = rtm::matrix_set(
                                        rtm::vector_load(&clipping_context->_matrixForMask.GetArray()[4 * 0]),
                                        rtm::vector_load(&clipping_context->_matrixForMask.GetArray()[4 * 1]),
                                        rtm::vector_load(&clipping_context->_matrixForMask.GetArray()[4 * 2]),
                                        rtm::vector_load(&clipping_context->_matrixForMask.GetArray()[4 * 3]));
                                    push_const.projection_matrix = rtm::matrix_transpose(proj_mat);
                                    const csmInt32 channelNo = clipping_context->_layoutChannelNo;
                                    CubismRenderer::CubismTextureColor* colorChannel = clipping_context->GetClippingManager()->GetChannelFlagAsColor(channelNo);
                                    push_const.channel_flag = { colorChannel->R, colorChannel->G, colorChannel->B, colorChannel->A };

                                    skr_live2d_model_get_drawable_colors(render_model->model_resource_id, clipDrawIndex, &push_const.multiply_color, &push_const.screen_color);
                                    csmRectF* rect = clipping_context->_layoutBounds;
                                    push_const.base_color = { rect->X * 2.0f - 1.0f, rect->Y * 2.0f - 1.0f, rect->GetRight() * 2.0f - 1.0f, rect->GetBottom() * 2.0f - 1.0f };

                                    const auto& cmds = render_model->primitive_commands;
                                    const auto& cmd = cmds[clipDrawIndex];
                                    auto visibility = skr_live2d_model_get_drawable_is_visible(render_model->model_resource_id, clipDrawIndex);
                                    auto& drawcall = mask_drawcalls.emplace().ref();
                                    if (!visibility)
                                    {
                                        drawcall.deprecated = true;
                                        drawcall.pipeline = proper_pipeline;
                                    }
                                    else
                                    {
                                        drawcall.pipeline = proper_pipeline;
                                        drawcall.push_const_name = push_constants_name;
                                        drawcall.push_const = (const uint8_t*)(&push_const);
                                        drawcall.index_buffer = *cmd.ibv;
                                        drawcall.vertex_buffers = cmd.vbvs.data();
                                        drawcall.vertex_buffer_count = (uint32_t)cmd.vbvs.size();
                                        {
                                            auto texture_view = skr_live2d_render_model_get_texture_view(render_model, clipDrawIndex);
                                            drawcall.bind_table = render_model->mask_bind_tables.find(texture_view).value();
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        };
        sugoiQ_get_views(effect_query, SUGOI_LAMBDA(updateMaskF));
    }

    inline CGPURenderPipelineId get_pipeline() const
    {
        switch ((uint32_t)sample_count)
        {
        case 1:
            return pipeline;
        case 2:
            return msaa2_pipeline;
        case 4:
            return msaa4_pipeline;
        case 8:
            return msaa8_pipeline;
        default:
            return pipeline;
        }
        return pipeline;
    }
    inline CGPURenderPipelineId get_mask_pipeline() const
    {
        switch ((uint32_t)sample_count)
        {
        case 1:
            return mask_pipeline;
        case 2:
            return msaa2_mask_pipeline;
        case 4:
            return msaa4_mask_pipeline;
        case 8:
            return msaa8_mask_pipeline;
        default:
            return mask_pipeline;
        }
        return mask_pipeline;
    }

protected:
    const char8_t* color_texture_name = u8"color_texture";
    void updateTexture(skr_live2d_render_model_id render_model)
    {
        SkrZoneScopedN("Live2D::updateTexture");

        // create descriptor sets if not existed
        const auto ib_c = render_model->index_buffer_views.size();
        for (uint32_t j = 0; j < ib_c; j++)
        {
            auto texture_view = skr_live2d_render_model_get_texture_view(render_model, j);
            {
                if (!render_model->bind_tables.contains(texture_view))
                {
                    SkrZoneScopedN("Live2D::createBindTable");

                    CGPUXBindTableDescriptor bind_table_desc = {};
                    bind_table_desc.root_signature = pipeline->root_signature;
                    bind_table_desc.names = &color_texture_name;
                    bind_table_desc.names_count = 1;
                    auto bind_table = cgpux_create_bind_table(pipeline->device, &bind_table_desc);
                    render_model->bind_tables.add(texture_view, bind_table);

                    CGPUDescriptorData datas[1] = {};
                    datas[0] = make_zeroed<CGPUDescriptorData>();
                    datas[0].by_name.name = color_texture_name;
                    datas[0].count = 1;
                    datas[0].textures = &texture_view;
                    cgpux_bind_table_update(bind_table, datas, 1);
                }
            }
            {
                if (!render_model->mask_bind_tables.contains(texture_view))
                {
                    SkrZoneScopedN("Live2D::createBindTable");

                    CGPUXBindTableDescriptor bind_table_desc = {};
                    bind_table_desc.root_signature = mask_pipeline->root_signature;
                    bind_table_desc.names = &color_texture_name;
                    bind_table_desc.names_count = 1;
                    auto bind_table = cgpux_create_bind_table(pipeline->device, &bind_table_desc);
                    render_model->mask_bind_tables.add(texture_view, bind_table);

                    CGPUDescriptorData datas[1] = {};
                    datas[0] = make_zeroed<CGPUDescriptorData>();
                    datas[0].by_name.name = color_texture_name;
                    datas[0].count = 1;
                    datas[0].textures = &texture_view;
                    cgpux_bind_table_update(bind_table, datas, 1);
                }
            }
        }
    }

    static const void* getVBData(skr_live2d_render_model_id render_model, uint32_t index, uint32_t& out_vcount)
    {
        const auto model_resource = render_model->model_resource_id;
        const void* pSrc = nullptr;
        // pos-uv-pos-uv...
        if (index % 2 == 0)
        {
            pSrc = skr_live2d_model_get_drawable_vertex_positions(
                model_resource, index / 2, &out_vcount);
        }
        else
        {
            pSrc = skr_live2d_model_get_drawable_vertex_uvs(
                model_resource, (index - 1) / 2, &out_vcount);
        }
        return pSrc;
    }

    void updateModelMotion(skr::render_graph::RenderGraph* render_graph, skr_live2d_render_model_id render_model)
    {
        SkrZoneScopedN("Live2D::updateModelMotion");

        const auto model_resource = render_model->model_resource_id;
        if (!motion_timers.contains(render_model))
            motion_timers.try_add_default(render_model);
        last_ms = skr_timer_get_msec(&motion_timers.find(render_model).value(), true);
        static float delta_sum = 0.f;
        delta_sum += ((float)last_ms / 1000.f);
        if (delta_sum > (1.f / kMotionFramesPerSecond))
        {
            skr_live2d_model_update(model_resource, delta_sum);
            delta_sum = 0.f;
            const auto vb_c = render_model->vertex_buffer_views.size();
            // update buffer
            if (render_model->use_dynamic_buffer && vb_c) // direct copy vertices to CVV buffer
            {
                for (uint32_t j = 0; j < vb_c; j++)
                {
                    auto& view = render_model->vertex_buffer_views[j];
                    uint32_t vcount = 0;
                    const void* pSrc = getVBData(render_model, j, vcount);
                    if (render_model->use_dynamic_buffer) // direct copy vertices to CVV buffer
                    {
                        memcpy((uint8_t*)view.buffer->info->cpu_mapped_address + view.offset, pSrc, vcount * view.stride);
                    }
                }
            }
            else if (vb_c)
            {
                uint64_t totalVertexSize = 0;
                skr::Map<CGPUBufferId, skr::render_graph::BufferHandle> imported_vbs_map;
                skr::stl_vector<skr::render_graph::BufferHandle> imported_vbs;
                skr::stl_vector<uint64_t> vb_sizes;
                skr::stl_vector<uint64_t> vb_offsets;
                if (!render_model->use_dynamic_buffer)
                {
                    imported_vbs.resize(vb_c);
                    vb_sizes.resize(vb_c);
                    vb_offsets.resize(vb_c);
                }
                for (uint32_t j = 0; j < vb_c; j++)
                {
                    auto& view = render_model->vertex_buffer_views[j];
                    uint32_t vcount = 0;
                    const void* pSrc = getVBData(render_model, j, vcount);
                    (void)pSrc;
                    imported_vbs_map.add(view.buffer, render_graph->create_buffer([=](skr::render_graph::RenderGraph& g, skr::render_graph::BufferBuilder& builder) {
                        skr::String name = skr::format(u8"live2d_vb-{}{}", (uint64_t)render_model, j);
                        builder.set_name((const char8_t*)name.c_str())
                            .import(view.buffer, CGPU_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER);
                    }));
                    imported_vbs[j] = imported_vbs_map.find(view.buffer).value();
                    vb_sizes[j] = vcount * view.stride;
                    vb_offsets[j] = view.offset;
                    totalVertexSize += vcount * view.stride;
                }
                if (totalVertexSize)
                {
                    namespace rg = skr::render_graph;

                    auto upload_buffer = render_graph->create_buffer(
                        [=](rg::RenderGraph& g, rg::BufferBuilder& builder) {
                            SkrZoneScopedN("ConstructUploadPass");

                            skr::String name = skr::format(u8"live2d_upload-{}", (uint64_t)render_model);
                            builder.set_name((const char8_t*)name.c_str())
                                .size(totalVertexSize)
                                .with_tags(kRenderGraphDefaultResourceTag)
                                .as_upload_buffer();
                        });
                    render_graph->add_copy_pass(
                        [=](rg::RenderGraph& g, rg::CopyPassBuilder& builder) {
                            SkrZoneScopedN("ConstructCopyPass");
                            skr::String name = skr::format(u8"live2d_copy-{}", (uint64_t)render_model);
                            builder.set_name((const char8_t*)name.c_str());
                            uint64_t range_cursor = 0;
                            for (uint32_t j = 0; j < vb_c; j++)
                            {
                                const auto vb_size = vb_sizes[j];
                                builder.buffer_to_buffer(
                                    upload_buffer.range(range_cursor, range_cursor + vb_size),
                                    imported_vbs[j].range(vb_offsets[j], vb_offsets[j] + vb_size));
                                range_cursor += vb_size;
                            }
                        },
                        [upload_buffer_hdl = upload_buffer, vb_c, render_model](rg::RenderGraph& g, rg::CopyPassContext& context) {
                            auto upload_buffer = context.resolve(upload_buffer_hdl);
                            uint8_t* range_cursor = (uint8_t*)upload_buffer->info->cpu_mapped_address;
                            for (uint32_t j = 0; j < vb_c; j++)
                            {
                                auto& view = render_model->vertex_buffer_views[j];
                                uint32_t vcount = 0;
                                const void* pSrc = getVBData(render_model, j, vcount);
                                memcpy(range_cursor, pSrc, vcount * view.stride);
                                range_cursor += vcount * view.stride;
                            }
                        });
                }
            }
            if (auto clipping_manager = render_model->clipping_manager)
            {
                clipping_manager->SetupClippingContext(*model_resource->model->GetModel(), kUseHighPrecisionMask);
            }
        }
    }

    void prepare_pipeline_settings();
    void prepare_pipeline(skr::RenderDevice* renderer);
    void prepare_mask_pipeline(skr::RenderDevice* renderer);
    void free_pipeline(skr::RenderDevice* renderer);
    void free_mask_pipeline(skr::RenderDevice* renderer);
    uint32_t* read_shader_bytes(skr::RenderDevice* renderer, const char8_t* name, uint32_t* out_length);
    CGPUShaderLibraryId create_shader_library(skr::RenderDevice* renderer, const char8_t* name, ECGPUShaderStage stage);

    struct PushConstants
    {
        rtm::matrix4x4f projection_matrix;
        rtm::matrix4x4f clip_matrix;
        skr_float4_t base_color;
        skr_float4_t multiply_color;
        skr_float4_t screen_color;
        skr_float4_t channel_flag;
        float use_mask;
        float pad0;
        float pad1;
        float pad2;
    };
    skr::Map<skr_live2d_render_model_id, skr::stl_vector<PushConstants>> push_constants;
    skr::Map<skr_live2d_render_model_id, skr::stl_vector<PushConstants>> mask_push_constants;

    CGPUVertexLayout vertex_layout = {};
    CGPURasterizerStateDescriptor rs_state = {};
    CGPUDepthStateDescriptor depth_state = {};

    CGPURenderPipelineId pipeline = nullptr;
    CGPURenderPipelineId msaa2_pipeline = nullptr;
    CGPURenderPipelineId msaa4_pipeline = nullptr;
    CGPURenderPipelineId msaa8_pipeline = nullptr;
    CGPURenderPipelineId mask_pipeline = nullptr;
    CGPURenderPipelineId msaa2_mask_pipeline = nullptr;
    CGPURenderPipelineId msaa4_mask_pipeline = nullptr;
    CGPURenderPipelineId msaa8_mask_pipeline = nullptr;
};
Live2DRendererImpl* live2d_effect = SkrNew<Live2DRendererImpl>();

uint32_t* Live2DRendererImpl::read_shader_bytes(skr::RenderDevice* render_device, const char8_t* name, uint32_t* out_length)
{
    const auto cgpu_device = render_device->get_cgpu_device();
    const auto backend = cgpu_device->adapter->instance->backend;
    skr::String shader_name = name;
    shader_name.append(backend == ::CGPU_BACKEND_D3D12 ? u8".dxil" : u8".spv");
    auto shader_file = skr_vfs_fopen(resource_vfs, shader_name.c_str(), SKR_FM_READ_BINARY, SKR_FILE_CREATION_OPEN_EXISTING);
    const uint32_t shader_length = (uint32_t)skr_vfs_fsize(shader_file);
    auto shader_bytes = (uint32_t*)sakura_malloc(shader_length);
    skr_vfs_fread(shader_file, shader_bytes, 0, shader_length);
    skr_vfs_fclose(shader_file);
    if (out_length) *out_length = shader_length;
    return shader_bytes;
}

CGPUShaderLibraryId Live2DRendererImpl::create_shader_library(skr::RenderDevice* render_device, const char8_t* name, ECGPUShaderStage stage)
{
    const auto cgpu_device = render_device->get_cgpu_device();
    uint32_t shader_length = 0;
    uint32_t* shader_bytes = read_shader_bytes(render_device, name, &shader_length);
    CGPUShaderLibraryDescriptor shader_desc = {};
    shader_desc.name = name;
    shader_desc.code = shader_bytes;
    shader_desc.code_size = shader_length;
    CGPUShaderLibraryId shader = cgpu_create_shader_library(cgpu_device, &shader_desc);
    sakura_free(shader_bytes);
    return shader;
}

void Live2DRendererImpl::prepare_pipeline_settings()
{
    vertex_layout.attributes[0] = { u8"pos", 1, CGPU_FORMAT_R32G32_SFLOAT, 0, 0, sizeof(skr_float2_t), CGPU_INPUT_RATE_VERTEX };
    vertex_layout.attributes[1] = { u8"uv", 1, CGPU_FORMAT_R32G32_SFLOAT, 1, 0, sizeof(skr_float2_t), CGPU_INPUT_RATE_VERTEX };
    vertex_layout.attribute_count = 2;

    rs_state.cull_mode = CGPU_CULL_MODE_NONE;
    rs_state.fill_mode = CGPU_FILL_MODE_SOLID;
    rs_state.front_face = CGPU_FRONT_FACE_CCW;
    rs_state.slope_scaled_depth_bias = 0.f;
    rs_state.enable_depth_clamp = true;
    rs_state.enable_scissor = true;
    rs_state.enable_multi_sample = false;
    rs_state.depth_bias = 0;

    depth_state.depth_write = false;
    depth_state.depth_test = false;
}

void Live2DRendererImpl::prepare_pipeline(skr::RenderDevice* render_device)
{
    const auto cgpu_device = render_device->get_cgpu_device();

    CGPUShaderLibraryId vs = create_shader_library(render_device, u8"shaders/live2d.vertex_shader", CGPU_SHADER_STAGE_VERT);
    CGPUShaderLibraryId ps = create_shader_library(render_device, u8"shaders/live2d.model_fs", CGPU_SHADER_STAGE_FRAG);

    CGPUShaderEntryDescriptor ppl_shaders[2];
    CGPUShaderEntryDescriptor& ppl_vs = ppl_shaders[0];
    ppl_vs.library = vs;
    ppl_vs.stage = CGPU_SHADER_STAGE_VERT;
    ppl_vs.entry = u8"vertex_shader";
    CGPUShaderEntryDescriptor& ppl_ps = ppl_shaders[1];
    ppl_ps.library = ps;
    ppl_ps.stage = CGPU_SHADER_STAGE_FRAG;
    ppl_ps.entry = u8"model_fs";

    const char8_t* static_sampler_name = u8"color_sampler";
    auto static_sampler = render_device->get_linear_sampler();
    auto rs_desc = make_zeroed<CGPURootSignatureDescriptor>();
    rs_desc.push_constant_count = 1;
    rs_desc.push_constant_names = &push_constants_name;
    rs_desc.shader_count = 2;
    rs_desc.shaders = ppl_shaders;
    rs_desc.pool = render_device->get_root_signature_pool();
    rs_desc.static_sampler_count = 1;
    rs_desc.static_sampler_names = &static_sampler_name;
    rs_desc.static_samplers = &static_sampler;
    rs_desc.name = u8"Live2DDraw-RootSignature";
    auto root_sig = cgpu_create_root_signature(cgpu_device, &rs_desc);

    CGPURenderPipelineDescriptor rp_desc = {};
    rp_desc.root_signature = root_sig;
    rp_desc.prim_topology = CGPU_PRIM_TOPO_TRI_LIST;
    rp_desc.vertex_layout = &vertex_layout;
    rp_desc.vertex_shader = &ppl_vs;
    rp_desc.fragment_shader = &ppl_ps;
    rp_desc.render_target_count = 1;
    rp_desc.color_formats = &live2d_mask_format;
    rp_desc.depth_stencil_format = live2d_depth_format;

    CGPUBlendStateDescriptor blend_state = {};
    blend_state.blend_modes[0] = CGPU_BLEND_MODE_ADD;
    blend_state.blend_alpha_modes[0] = CGPU_BLEND_MODE_ADD;
    blend_state.masks[0] = CGPU_COLOR_MASK_ALL;
    blend_state.independent_blend = false;

    // Normal
    blend_state.src_factors[0] = CGPU_BLEND_CONST_SRC_ALPHA;
    blend_state.dst_factors[0] = CGPU_BLEND_CONST_ONE_MINUS_SRC_ALPHA;
    blend_state.src_alpha_factors[0] = CGPU_BLEND_CONST_ONE;
    blend_state.dst_alpha_factors[0] = CGPU_BLEND_CONST_ONE_MINUS_SRC_ALPHA;

    // Multiply
    blend_state.src_factors[0] = CGPU_BLEND_CONST_ONE;
    blend_state.dst_factors[0] = CGPU_BLEND_CONST_ONE_MINUS_SRC_ALPHA;
    blend_state.src_alpha_factors[0] = CGPU_BLEND_CONST_ONE;
    blend_state.dst_alpha_factors[0] = CGPU_BLEND_CONST_ONE_MINUS_SRC_ALPHA;

    rp_desc.blend_state = &blend_state;
    rp_desc.rasterizer_state = &rs_state;
    rp_desc.depth_state = &depth_state;
    pipeline = cgpu_create_render_pipeline(cgpu_device, &rp_desc);
    rp_desc.sample_count = CGPU_SAMPLE_COUNT_2;
    msaa2_pipeline = cgpu_create_render_pipeline(cgpu_device, &rp_desc);
    rp_desc.sample_count = CGPU_SAMPLE_COUNT_4;
    msaa4_pipeline = cgpu_create_render_pipeline(cgpu_device, &rp_desc);
    rp_desc.sample_count = CGPU_SAMPLE_COUNT_8;
    msaa8_pipeline = cgpu_create_render_pipeline(cgpu_device, &rp_desc);

    cgpu_free_shader_library(vs);
    cgpu_free_shader_library(ps);
}

void Live2DRendererImpl::free_pipeline(skr::RenderDevice* renderer)
{
    auto sig_to_free = pipeline->root_signature;
    cgpu_free_render_pipeline(pipeline);
    cgpu_free_render_pipeline(msaa2_pipeline);
    cgpu_free_render_pipeline(msaa4_pipeline);
    cgpu_free_render_pipeline(msaa8_pipeline);
    cgpu_free_root_signature(sig_to_free);
}

void Live2DRendererImpl::prepare_mask_pipeline(skr::RenderDevice* render_device)
{
    const auto cgpu_device = render_device->get_cgpu_device();

    CGPUShaderLibraryId vs = create_shader_library(render_device, u8"shaders/live2d.vertex_shader", CGPU_SHADER_STAGE_VERT);
    CGPUShaderLibraryId ps = create_shader_library(render_device, u8"shaders/live2d.mask_fs", CGPU_SHADER_STAGE_FRAG);

    CGPUShaderEntryDescriptor ppl_shaders[2];
    CGPUShaderEntryDescriptor& ppl_vs = ppl_shaders[0];
    ppl_vs.library = vs;
    ppl_vs.stage = CGPU_SHADER_STAGE_VERT;
    ppl_vs.entry = u8"vertex_shader";
    CGPUShaderEntryDescriptor& ppl_ps = ppl_shaders[1];
    ppl_ps.library = ps;
    ppl_ps.stage = CGPU_SHADER_STAGE_FRAG;
    ppl_ps.entry = u8"mask_fs";

    const char8_t* static_sampler_name = u8"color_sampler";
    auto static_sampler = render_device->get_linear_sampler();
    auto rs_desc = make_zeroed<CGPURootSignatureDescriptor>();
    rs_desc.push_constant_count = 1;
    rs_desc.push_constant_names = &push_constants_name;
    rs_desc.shader_count = 2;
    rs_desc.shaders = ppl_shaders;
    rs_desc.pool = render_device->get_root_signature_pool();
    rs_desc.static_sampler_count = 1;
    rs_desc.static_sampler_names = &static_sampler_name;
    rs_desc.static_samplers = &static_sampler;
    rs_desc.name = u8"Live2DMask-RootSignature";
    auto root_sig = cgpu_create_root_signature(cgpu_device, &rs_desc);

    CGPURenderPipelineDescriptor rp_desc = {};
    rp_desc.root_signature = root_sig;
    rp_desc.prim_topology = CGPU_PRIM_TOPO_TRI_LIST;
    rp_desc.vertex_layout = &vertex_layout;
    rp_desc.vertex_shader = &ppl_vs;
    rp_desc.fragment_shader = &ppl_ps;
    rp_desc.render_target_count = 1;
    rp_desc.color_formats = &live2d_mask_format;
    rp_desc.depth_stencil_format = live2d_depth_format;

    CGPUBlendStateDescriptor blend_state = {};
    blend_state.blend_modes[0] = CGPU_BLEND_MODE_ADD;
    blend_state.blend_alpha_modes[0] = CGPU_BLEND_MODE_ADD;
    blend_state.masks[0] = CGPU_COLOR_MASK_ALL;
    blend_state.independent_blend = false;

    // Mask
    blend_state.src_factors[0] = CGPU_BLEND_CONST_ZERO;
    blend_state.dst_factors[0] = CGPU_BLEND_CONST_ONE_MINUS_SRC_COLOR;
    blend_state.src_alpha_factors[0] = CGPU_BLEND_CONST_ZERO;
    blend_state.dst_alpha_factors[0] = CGPU_BLEND_CONST_ONE_MINUS_SRC_ALPHA;

    // Multiply
    // blend_state.src_factors[0] = CGPU_BLEND_CONST_ONE;
    // blend_state.dst_factors[0] = CGPU_BLEND_CONST_ONE_MINUS_SRC_ALPHA;
    // blend_state.src_alpha_factors[0] = CGPU_BLEND_CONST_ONE;
    // blend_state.dst_alpha_factors[0] = CGPU_BLEND_CONST_ONE_MINUS_SRC_ALPHA;

    rp_desc.blend_state = &blend_state;
    rp_desc.rasterizer_state = &rs_state;
    rp_desc.depth_state = &depth_state;
    mask_pipeline = cgpu_create_render_pipeline(cgpu_device, &rp_desc);
    rp_desc.sample_count = CGPU_SAMPLE_COUNT_2;
    msaa2_mask_pipeline = cgpu_create_render_pipeline(cgpu_device, &rp_desc);
    rp_desc.sample_count = CGPU_SAMPLE_COUNT_4;
    msaa4_mask_pipeline = cgpu_create_render_pipeline(cgpu_device, &rp_desc);
    rp_desc.sample_count = CGPU_SAMPLE_COUNT_8;
    msaa8_mask_pipeline = cgpu_create_render_pipeline(cgpu_device, &rp_desc);

    cgpu_free_shader_library(vs);
    cgpu_free_shader_library(ps);
}

void Live2DRendererImpl::free_mask_pipeline(skr::RenderDevice* renderer)
{
    auto sig_to_free = mask_pipeline->root_signature;
    cgpu_free_render_pipeline(mask_pipeline);
    cgpu_free_render_pipeline(msaa2_mask_pipeline);
    cgpu_free_render_pipeline(msaa4_mask_pipeline);
    cgpu_free_render_pipeline(msaa8_mask_pipeline);
    cgpu_free_root_signature(sig_to_free);
}

skr::Live2DRenderer* skr::Live2DRenderer::Create()
{
    return SkrNew<Live2DRendererImpl>();
}

void skr::Live2DRenderer::Destroy(Live2DRenderer* renderer)
{
    SkrDelete(renderer);
}

skr::Live2DRenderer::~Live2DRenderer()
{
}

void skr_live2d_render_view_reset(live2d_render_view_t* view)
{
    view->clear_color = { 1.f, 1.f, 1.f, 0.f };
    new (&view->view_matrix) Csm::CubismViewMatrix();
    view->device_to_screen.LoadIdentity();
}

void skr_live2d_render_view_set_screen(live2d_render_view_t* view, uint32_t width, uint32_t height)
{
    float ratio = static_cast<float>(width) / static_cast<float>(height);
    float left = -ratio;
    float right = ratio;
    float bottom = kLive2DViewLogicalLeft;
    float top = kLive2DViewLogicalRight;

    view->view_matrix.SetScreenRect(left, right, bottom, top);
    view->view_matrix.Scale(kLive2DViewScale, kLive2DViewScale);

    view->device_to_screen.LoadIdentity();
    if (width > height)
    {
        float screen_w = fabsf(right - left);
        view->device_to_screen.ScaleRelative(screen_w / width, -screen_w / width);
    }
    else
    {
        float screen_w = fabsf(top - bottom);
        view->device_to_screen.ScaleRelative(screen_w / height, -screen_w / height);
    }
    view->device_to_screen.TranslateRelative(width * -0.5f, height * -0.5f);

    // 表示範囲の設定
    view->view_matrix.SetMaxScale(kLive2DViewMaxScale); // 限界拡大率
    view->view_matrix.SetMinScale(kLive2DViewMinScale); // 限界縮小率

    // 表示できる最大範囲
    view->view_matrix.SetMaxScreenRect(
        kLive2DViewLogicalMaxLeft,
        kLive2DViewLogicalMaxRight,
        kLive2DViewLogicalMaxBottom,
        kLive2DViewLogicalMaxTop);
}

void skr_live2d_render_view_transform_screen(live2d_render_view_t* view, float deviceX, float deviceY)
{
    view->device_to_screen.TransformX(deviceX);
    view->device_to_screen.TransformY(deviceY);
}

void skr_live2d_render_view_transform_view(live2d_render_view_t* view, float deviceX, float deviceY)
{
    float screenX = view->device_to_screen.TransformX(deviceX); // 論理座標変換した座標を取得。
    view->view_matrix.InvertTransformX(screenX);                // 拡大、縮小、移動後の値。

    float screenY = view->device_to_screen.TransformY(deviceY); // 論理座標変換した座標を取得。
    view->view_matrix.InvertTransformY(screenY);                // 拡大、縮小、移動後の値。
}